﻿/* HEADER
 * ------
 * © 2009 by Salomon Zwecker 
 * modified by:
 * - 
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;



namespace Shapes.Geometry
{
    /// <summary>
    /// Represents a 2D-Transformation with Bounding-Rectangle information
    /// </summary>
    public class Transformation2D : ICloneable, ITransformable2D

    {

        internal Vector2 _Position = Vector2.Zero;
        internal Vector2 _Origin = Vector2.Zero;
        internal Angle _Rotation;
        internal Vector2 _Scale = Vector2.One;

        internal float _Width;
        internal float _Height;


        internal bool _IsDirty = true;
        internal Vector2 _Center;
        float _Up, _Down, _Left, _Right;

        /// <summary>
        /// Event is called when the transformation is changed
        /// </summary>
        public event Action<Transformation2D> OnChange;

        #region Properties
        /// <summary>
        /// Get the width of the Bounding-Area (without transformation)
        /// </summary>
        public float Width { get { return _Width; } }
        /// <summary>
        /// Get the height of the Bounding-Area (without transformation)
        /// </summary>
        public float Height { get { return _Height; } }
        /// <summary>
        /// The absolute width of the Bounding-Area
        /// </summary>
        public float AbsoluteWidth { get { return Right - Left; } }
        /// <summary>
        /// The absolute height of the Bounding-Area
        /// </summary>
        public float AbsoluteHeight { get { return Down - Up; } }
        /// <summary>
        /// The position of the Origin in global coordinates
        /// </summary>
        public Vector2 Position {
            get { return _Position + _Origin; } 
            set { 
                _Position = value - _Origin; 
                _IsDirty = true;
                CallOnChange();
            } 
        }

        /// <summary>
        /// The torsion-point of the bounding in pixels (Vector2.Zero is the left upper corner)
        /// </summary>
        public Vector2 Origin { 
            get { return _Origin; } 
            set { 
                _Origin = value; 
                _IsDirty = true;
                CallOnChange();
            }
        }

        /// <summary>
        /// The orientation of the bounding in radians
        /// </summary>
        public Angle Rotation { 
            get { return _Rotation; } 
            set { 
                _Rotation = value; 
                _IsDirty = true;
                CallOnChange();
            }
        }
        /// <summary>
        /// the scale of the bounding (Vector2.One is original size)
        /// </summary>
        public Vector2 Scale { 
            get { return _Scale; } 
            set { 
                _Scale = value; 
                _IsDirty = true;
                CallOnChange();
            }
        }

        /// <summary>
        /// The position of the Center of the Rectangle
        /// </summary>
        internal Vector2 Center
        {
            get { 
                CalculateTransform(); 
                return _Center; 
            }
            set { 
                CalculateTransform();
                _Position = GetPositionFromCenter(value);
                _IsDirty = true;
                CallOnChange();
            }
        }
        
        /// <summary>
        /// Get the vertical position of the most upper corner
        /// </summary>
        public float Up {  get { CalculateTransform(); return _Up; } }
        /// <summary>
        /// Get the vertical position of the most lower corner
        /// </summary>
        public float Down { get { CalculateTransform(); return _Down; } }
        /// <summary>
        /// Get the horizontal position of the corner on the most left
        /// </summary>
        public float Left { get { CalculateTransform(); return _Left; } }
        /// <summary>
        /// Get the horizontal position of the corner on the most right
        /// </summary>
        public float Right { get { CalculateTransform(); return _Right; } }
        #endregion


        /// <summary>
        /// Creates a new BoundingRectangle with default values
        /// </summary>
        public Transformation2D()
        {
            _Position = Vector2.Zero;
            _Width = 1;
            _Height = 1;
        }
        /// <summary>
        /// Creates a new Bounding Rectangle
        /// </summary>
        /// <param name="position">the Position of the left upper corner of the rectangle</param>
        /// <param name="width">the width of the rectangle</param>
        /// <param name="height">the height of the rectangle</param>
        public Transformation2D(Vector2 position, float width, float height)
        {
            _Position = position;
            
            _Width = width;
            _Height = height;
            _IsDirty = true;
        }
        /// <summary>
        /// Creates a new Bounding Rectangle
        /// </summary>
        /// <param name="position">the Position of the left upper corner of the rectangle</param>
        /// <param name="width">the width of the rectangle</param>
        /// <param name="height">the height of the rectangle</param>
        /// <param name="origin">the position of the origin</param>
        /// <param name="rotation">the rotation</param>
        /// <param name="scale">the scale factor</param>
        public Transformation2D(Vector2 position, float width, float height, Vector2 origin, Angle rotation, Vector2 scale)
        {
            _Origin = origin;
            Position = position;

            _Width = width;
            _Height = height;

            _Rotation = rotation;
            _Scale = scale;

            _IsDirty = true;
        }

        /// <summary>
        /// Determines wether a specified bounding rectangle ( = Transformation2D) intersects with this Transformation2D
        /// </summary>
        /// <param name="rect">the Rectangle to check</param>
        /// <returns>true if the rectangles intersecting or containing the other</returns>
        public bool Intersects(Transformation2D rect)
        {
            CalculateTransform();
            rect.CalculateTransform();

            bool isUpInRange = ((_Up >= rect._Up) && (_Up <= rect._Down));
            bool isDownInRange = ((_Down >= rect._Up) && (_Down <= rect._Down));
            bool isLeftInRange = ((_Left >= rect._Left) && (_Left <= rect._Right));
            bool isRightInRange = ((_Right >= rect._Left) && (_Right <= rect._Right));

            if ((isUpInRange || isDownInRange)
                && (isLeftInRange || isRightInRange))
                return true;

            bool isVerticalInside = ((_Up < rect._Up) && (_Down > rect._Down));
            bool isHorizontalInside = ((_Left < rect._Left) && (_Right > rect._Right));

            if ((isVerticalInside || isUpInRange || isDownInRange)
                && (isHorizontalInside || isLeftInRange || isRightInRange))
                return true;

            return false;
        }

        /// <summary>
        /// Checks wether a point is inside or at an edge of the rectangle
        /// </summary>
        /// <param name="point">the point to check</param>
        /// <returns>true if the point is inside</returns>
        public bool Contains(Vector2 point)
        {
            TransformGlobalToLocal(ref point);
            return Contains(ref point);
        }
        internal bool Contains(ref Vector2 point)
        {
            return ((point.X >= 0 && point.Y >= 0)
                && (point.X <= _Width && point.Y <= _Height));

        }
        /// <summary>
        /// Transforms the given point from local transformation coordinates to screen space coordinates
        /// </summary>
        /// <param name="position">the local position</param>
        /// <returns>the global position</returns>
        public Vector2 TransformLocalToGlobal(Vector2 position)
        {
            TransformLocalToGlobal(ref position);
            return position;
        }
        internal void TransformLocalToGlobal(ref Vector2 outPosition)
        {
            outPosition.X -= _Origin.X;
            outPosition.Y -= _Origin.Y;

            if (_Rotation.Radians == 0)
            {
                outPosition.X *= _Scale.X;
                outPosition.Y *= _Scale.Y;
            }
            else
            {
                float length = outPosition.Length();
                float rot = (float)Math.Atan2(outPosition.Y, outPosition.X) - _Rotation.Radians;

                outPosition.X = ((float)Math.Cos(rot) * length) * _Scale.X;
                outPosition.Y = ((float)Math.Sin(rot) * length) * _Scale.Y;
            }
            outPosition.X += Position.X;// *_Scale.X;
            outPosition.Y += Position.Y;// *_Scale.Y;

        }
        /// <summary>
        /// Transforms the given point from screen space coordinates to local transformation coordinates
        /// </summary>
        /// <param name="position">the global position</param>
        /// <returns>the local position</returns>
        public Vector2 TransformGlobalToLocal(Vector2 position)
        {
            TransformGlobalToLocal(ref position);
            return position;
        }
        internal void TransformGlobalToLocal(ref Vector2 outPosition)
        {
            outPosition.X -= _Position.X + _Origin.X;
            outPosition.Y -= _Position.Y + _Origin.Y;

            if (_Rotation.Radians == 0)
            {
                outPosition.X = (outPosition.X / _Scale.X) + _Origin.X;
                outPosition.Y = (outPosition.Y / _Scale.Y) + _Origin.Y;

                return;
            }
        
            float length = outPosition.Length();
            float rot = (float)Math.Atan2(outPosition.Y, outPosition.X) + _Rotation.Radians;

            outPosition.X = (((float)Math.Cos(rot) * length) / _Scale.X) + _Origin.X;
            outPosition.Y = (((float)Math.Sin(rot) * length) / _Scale.Y) + _Origin.Y;
        }

        // calculates the new position data of several points to keep the bounding information up to date.
        void CalculateTransform()
        {
            if (_IsDirty)
            {
                Vector2 point1 = Vector2.Zero;
                Vector2 point2 = new Vector2(_Width, 0);
                Vector2 point3 = new Vector2(0, _Height);
                Vector2 point4 = new Vector2(_Width, _Height);
                TransformLocalToGlobal(ref point1);
                TransformLocalToGlobal(ref point2);
                TransformLocalToGlobal(ref point3);
                TransformLocalToGlobal(ref point4);

                _Up    = Math.Min(point1.Y, Math.Min(point2.Y, Math.Min(point3.Y, point4.Y)));
                _Down  = Math.Max(point1.Y, Math.Max(point2.Y, Math.Max(point3.Y, point4.Y)));
                _Left  = Math.Min(point1.X, Math.Min(point2.X, Math.Min(point3.X, point4.X)));
                _Right = Math.Max(point1.X, Math.Max(point2.X, Math.Max(point3.X, point4.X)));

                _Center = TransformLocalToGlobal(new Vector2(_Width / 2, _Height / 2));

                _IsDirty = false;
            }
        }

        /// <summary>
        /// Gets the bounding rectangle of the transformation which covers the whole transformed object.
        /// </summary>
        /// <returns></returns>
        public Rectangle GetBoundingRectangle()
        {
            return new Rectangle((int)Math.Floor(Left), (int)Math.Floor(Up), (int)Math.Ceiling(AbsoluteWidth), (int)Math.Ceiling(AbsoluteHeight));
        }
        /// <summary>
        /// Gets the rectangle of the object without position, scale or rotation 
        /// </summary>
        /// <returns></returns>
        public Rectangle GetRectangleWithoutTransformation()
        {
            return new Rectangle(0, 0, (int)Math.Round(_Width), (int)Math.Round(_Height));
        }

        /// <summary>
        /// Sets the Width and Height. Use this method only if you really know what you are doing! 
        /// </summary>
        /// <param name="width">the new width of the not-transformed bounding</param>
        /// <param name="height">the new height of the not-transformed bounding</param>
        public void SetDimension(float width, float height)
        {
            _Width = width;
            _Height = height;
        }

        /// <summary>
        /// returns the values of the transformation as a string
        /// </summary>
        /// <returns>the values of the rectangle as a string</returns>
        public override string ToString()
        {
            return "Transform: P[" + Position.X + ", " + Position.Y 
                + "] S[" + _Scale.X + ", " + _Scale.Y 
                + "] R[" + _Rotation.Radians + "] -- "

                + "Bounding: H[" + Left + " ~ " + Right + "] V[" + Up + " ~ " + Down + "]";
        }

        internal Vector2 GetPositionFromCenter(Vector2 newCenter)
        {
            return newCenter - (Center - _Position);
        }

        internal void CallOnChange()
        {
            if (OnChange != null)
                OnChange(this);
        }

        #region ICloneable Member

        /// <summary>
        /// Clones the Transformation2D
        /// </summary>
        /// <returns>a new object of the type Transformation2D</returns>
        public object Clone()
        {
            return new Transformation2D(Position, _Width, _Height, _Origin, _Rotation, _Scale);
        }

        #endregion


        /// <summary>
        /// fills the Transformation data with values from another Transformation2D instance
        /// </summary>
        /// <param name="other">the object to copy the values from</param>
        public void CopyFrom(Transformation2D other)
        {
            this._Center = other._Center;
            this._Down = other._Down;
            this._Height = other._Height;
            this._IsDirty = other._IsDirty;
            this._Left = other._Left;
            this._Origin = other._Origin;
            this._Position = other._Position;
            this._Right = other._Right;
            this._Rotation = other._Rotation;
            this._Scale = other._Scale;
            this._Up = other._Up;
            this._Width = other._Width;
        }
    }
}
